/*
* Copyright 2003-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.nodeEditor.memory;
import org.jetbrains.annotations.NotNull;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
/**
* User: shatalin
* Date: 30/12/15
*/
public class MemoryAnalyzer {
private static final String AGENT_CLASS_NAME = "jetbrains.mps.editor.Agent";
private static final String GET_INSTRUMENTATION_METHOD_NAME = "getInstrumentation";
static Instrumentation ourInstrumentation = getInstrumentation();
private static Instrumentation getInstrumentation() {
try {
Class<?> agentClass = Class.forName(AGENT_CLASS_NAME, true, ClassLoader.getSystemClassLoader());
if (agentClass == null) {
return null;
}
Method getter = getInstrumentationGetter(agentClass);
return (Instrumentation) getter.invoke(null);
} catch (ClassNotFoundException e) {
} catch (InvocationTargetException e) {
} catch (IllegalAccessException e) {
}
return null;
}
private static Method getInstrumentationGetter(Class agentClass) {
for (Method method : agentClass.getMethods()) {
if (GET_INSTRUMENTATION_METHOD_NAME.equals(method.getName())) {
return method;
}
}
return null;
}
private long myTotalSize = 0;
public boolean isValid() {
return ourInstrumentation != null;
}
public long getSize() {
return myTotalSize;
}
public void appendObject(Object o) {
myTotalSize += getObjectSize(o);
}
public void appendCollection(Collection c) {
if (c instanceof LinkedList) {
appendLinkedList((LinkedList) c);
} else if (c instanceof ArrayList) {
appendArrayList((ArrayList) c);
} else {
// TODO: support other collections here
appendObject(c);
}
}
public void appendLinkedList(LinkedList list) {
appendObject(list);
Field entryField = getFirstNonPrimitiveField(list.getClass());
if (entryField != null) {
appendField(list, entryField, list.size());
} else {
// approximation of the overhead for entries in LinkedList
myTotalSize += list.size() * 24;
}
}
public void appendArrayList(ArrayList list) {
appendObject(list);
Field entryField = getFirstNonPrimitiveField(list.getClass());
if (entryField != null) {
appendField(list, entryField, 1);
}
}
public void appendFirstNonPrimitiveField(Object o) {
Field field = getFirstNonPrimitiveField(o.getClass());
if (field != null) {
appendField(o, field, 1);
}
}
private Field getFirstNonPrimitiveField(Class cls) {
for (Field nextField : cls.getDeclaredFields()) {
if (!nextField.getType().isPrimitive()) {
return nextField;
}
}
return null;
}
public void appendField(Object o, String fieldName) {
Field field = null;
for (Class<?> clazz = o.getClass(); clazz != null && field == null; clazz = clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
}
}
assert field != null : "Field \"" + fieldName + "\" was not found in class: " + o.getClass();
appendField(o, field, 1);
}
private void appendField(Object o, @NotNull Field field, int counter) {
field.setAccessible(true);
try {
Object value = field.get(o);
if (value != null) {
myTotalSize += getObjectSize(value) * counter;
}
} catch (IllegalAccessException e) {
assert false : "Should never happen, message: " + e.getMessage();
}
}
private long getObjectSize(Object o) {
return ourInstrumentation.getObjectSize(o);
}
}